import { PACKAGE_NAME } from "./global";

/** 本地存储文件夹名，配套框架中的热更脚本 */
// var crypto = require("crypto");
var SparkMD5 = require("spark-md5");
var fs = require("fs");
var path = require("path");
var folder = "data";

class HotUpdateTool {
    projectPath: string;
    buildPath: string;
    assetsRootPath: string;

    create(options: any) {
        if (!this.isEnable(options)) return;

        this.init(options);
        this.mainJs(options);
        this.generateConfig(options);
        this.manifest(options);
        this.versionPackage(options);
    }

    isEnable(options) {
        var packageOptions = options.packages[PACKAGE_NAME];
        if (!packageOptions.hotUpdateEnable) {
            return false;
        }
        return true;
    }

    init(options) {
        this.projectPath = Editor.Project.path;
        this.buildPath = `${options.buildPath.replace('project:/', this.projectPath)}/${options.outputName}`;
        this.assetsRootPath = path.resolve(`${this.buildPath}/${folder}`);
        console.log('[' + PACKAGE_NAME + '] 初始化成功');
    }

    mainJs(options) {
        var packageOptions = options.packages[PACKAGE_NAME];
        var mainScriptPath = path.resolve(`${this.buildPath}/${folder}/main.js`);
        var mainScript = fs.readFileSync(mainScriptPath).toString('utf-8');
        mainScript =
            `// ---- 扩展注入热更新脚本开始 ----
(function () {
    if (typeof window.jsb === 'object') {
        jsb.fileUtils.addSearchPath(jsb.fileUtils.getWritablePath() + "${packageOptions.hotupdateSavePath}", true);
        var fileList = [];
        var storagePath = "${packageOptions.hotupdateSavePath}";
        var tempPath = storagePath + "_temp/";
        var baseOffset = tempPath.length;
        
        if (jsb.fileUtils.isDirectoryExist(tempPath) && !jsb.fileUtils.isFileExist(tempPath + 'project.manifest.temp')) {
            jsb.fileUtils.listFilesRecursively(tempPath, fileList);
            fileList.forEach(srcPath => {
                var relativePath = srcPath.substr(baseOffset);
                var dstPath = storagePath + relativePath;
                if (srcPath[srcPath.length - 1] === "/") {
                    jsb.fileUtils.createDirectory(dstPath)
                }
                else {
                    if (jsb.fileUtils.isFileExist(dstPath)) {
                        jsb.fileUtils.removeFile(dstPath)
                    }
                    jsb.fileUtils.renameFile(srcPath, dstPath);
                }
            })
            jsb.fileUtils.removeDirectory(tempPath);
        }
    }
})();
// ---- 扩展注入热更新脚本结束 ----` + mainScript;
        fs.writeFileSync(mainScriptPath, mainScript);
        console.log('[' + PACKAGE_NAME + '] 注入 main.js 成功');
    }

    manifest(options) {
        var packageOptions = options.packages[PACKAGE_NAME];
        if (packageOptions.hotUpdateType != "1")
            return;

        var remoteUrl = packageOptions.hotUpdateAddress;
        if (remoteUrl.endsWith('/')) {
            remoteUrl = remoteUrl.slice(0, -1);
        }

        var hotUpdateBuildNum = !isNaN(Number(packageOptions.hotUpdateBuildNum)) ? Number(packageOptions.hotUpdateBuildNum) : 1;
        var hotUpdateVersion = `${packageOptions.hotUpdateVersion.trim()}.${hotUpdateBuildNum.toFixed()}`;
        var projectManifestName = 'project.manifest';
        var versionManifestName = 'version.manifest';
        var destManifestPath = path.join(this.projectPath, "assets/resources");

        var packageUrl = `${remoteUrl}/${hotUpdateVersion}/`;
        var manifest = {
            packageUrl: packageUrl,
            version: hotUpdateVersion,
            remoteManifestUrl: `${remoteUrl}/${projectManifestName}`,
            remoteVersionUrl: `${remoteUrl}/${versionManifestName}`,
            assets: {},
            searchPaths: [],
        };

        // Iterate assets and src folder
        this.readDir(path.join(this.assetsRootPath, 'src'), manifest.assets);
        this.readDir(path.join(this.assetsRootPath, 'assets'), manifest.assets);
        this.readDir(path.join(this.assetsRootPath, 'jsb-adapter'), manifest.assets);
        // readFile(path.join(this.buildPath, 'main.js'), manifest.assets);

        var destManifest = path.join(destManifestPath, 'project.manifest');
        var destVersion = path.join(destManifestPath, 'version.manifest');

        this.mkdirSync(destManifestPath);

        fs.writeFile(destManifest, JSON.stringify(manifest), (err) => {
            if (err) throw err;
            console.log('Manifest successfully generated');
        });

        delete manifest.assets;
        delete manifest.searchPaths;
        fs.writeFile(destVersion, JSON.stringify(manifest), (err) => {
            if (err) throw err;
            console.log('Version successfully generated');
        });

        console.log('[' + PACKAGE_NAME + '] 生成清单文件 成功');
    }

    versionPackage(options) {
        var packageOptions = options.packages[PACKAGE_NAME];
        if (packageOptions.hotUpdateType != "2")
            return;

        var remoteUrl = packageOptions.hotUpdateAddress;
        if (remoteUrl.endsWith('/')) {
            remoteUrl = remoteUrl.slice(0, -1);
        }

        // 同版本构建次数
        var hotUpdateBuildNum = !isNaN(Number(packageOptions.hotUpdateBuildNum)) ? Number(packageOptions.hotUpdateBuildNum) : 1;
        var hotUpdateVersion = `${packageOptions.hotUpdateVersion.trim()}.${hotUpdateBuildNum.toFixed()}`;
        var assetsRootPath = path.resolve(`${this.buildPath}/${folder}`);
        var projectManifestName = 'project.manifest';
        var versionManifestName = 'version.manifest';
        var dest = path.join(this.projectPath, packageOptions.hotupdateOutPath);

        // 初始化 manifest
        var packageUrl = `${remoteUrl}/${hotUpdateVersion}/`;
        var manifest = {
            packageUrl: packageUrl,
            version: hotUpdateVersion,
            remoteManifestUrl: `${remoteUrl}/${projectManifestName}`,
            remoteVersionUrl: `${remoteUrl}/${versionManifestName}`,
            assets: {},
            searchPaths: [],
        };

        // Iterate assets and src folder
        this.readDir(path.join(assetsRootPath, 'src'), manifest.assets);
        this.readDir(path.join(assetsRootPath, 'jsb-adapter'), manifest.assets);
        // readFile(path.join(src, 'main.js'), manifest.assets)
        var srcAssetsDir = path.join(assetsRootPath, 'assets');
        this.readDir(path.join(srcAssetsDir, 'internal'), manifest.assets);
        this.readDir(path.join(srcAssetsDir, 'main'), manifest.assets);
        this.readDir(path.join(srcAssetsDir, 'resources'), manifest.assets);
        // this.readDir(path.join(srcAssetsDir, 'start-scene'), manifest.assets);
        this.mkdirSync(dest);

        var destFileDir = path.join(dest, 'ver-' + manifest.version);
        if (fs.existsSync(destFileDir)) {
            this.deleteDirSync(destFileDir);
        } 
        this.mkdirSync(destFileDir);

        var destManifest = path.join(destFileDir, 'project.manifest');
        var destVersion = path.join(destFileDir, 'version.manifest');

        fs.writeFile(destManifest, JSON.stringify(manifest), (err) => {
            if (err) throw err;
            console.log('[' + PACKAGE_NAME + '] project.manifest生成成功');
        });

        delete manifest.assets;
        delete manifest.searchPaths;
        fs.writeFile(destVersion, JSON.stringify(manifest), (err) => {
            if (err) throw err;
            console.log('[' + PACKAGE_NAME + '] version.manifest生成成功');
        });

        // 拷贝data
        var destVersionDir = path.join(destFileDir, manifest.version);
        this.mkdirSync(destVersionDir);

        this.mkdirSync(path.join(destVersionDir, 'src'));
        this.mkdirSync(path.join(destVersionDir, 'jsb-adapter'));
        var destAssetsDir = path.join(destVersionDir, 'assets');
        this.mkdirSync(destAssetsDir);
        this.mkdirSync(path.join(destAssetsDir, 'internal'));
        this.mkdirSync(path.join(destAssetsDir, 'main'));
        this.mkdirSync(path.join(destAssetsDir, 'resources'));
        // this.mkdirSync(path.join(destAssetsDir, 'start-scene'));
        
        // copyFile(path.join(src, 'main.js'), path.join(destVersionDir, 'main.js'))
        this.copyFileDir(path.join(assetsRootPath, 'src'), path.join(destVersionDir, 'src'));
        this.copyFileDir(path.join(assetsRootPath, 'jsb-adapter'), path.join(destVersionDir, 'jsb-adapter'));
        this.copyFileDir(path.join(srcAssetsDir, 'internal'), path.join(destAssetsDir, 'internal'));
        this.copyFileDir(path.join(srcAssetsDir, 'main'), path.join(destAssetsDir, 'main'));
        this.copyFileDir(path.join(srcAssetsDir, 'resources'), path.join(destAssetsDir, 'resources'));
        // this.copyFileDir(path.join(srcAssetsDir, 'start-scene'), path.join(destAssetsDir, 'start-scene'));

        console.log('[' + PACKAGE_NAME + '] 资源拷贝完成');
    }
    
    // 读取文件
    readDir(dir, obj) {
        try {
            var stat = fs.statSync(dir);
            if (!stat.isDirectory()) {
                return;
            }
            var subpaths = fs.readdirSync(dir), subpath, size, md5, compressed, relative;
            for (var i = 0; i < subpaths.length; ++i) {
                if (subpaths[i][0] === '.') {
                    continue;
                }
                subpath = path.join(dir, subpaths[i]);
                stat = fs.statSync(subpath);
                if (stat.isDirectory()) {
                    this.readDir(subpath, obj);
                }
                else if (stat.isFile()) {
                    // Size in Bytes
                    size = stat['size'];
                    md5 = SparkMD5.ArrayBuffer.hash(fs.readFileSync(subpath));
                    // md5 = crypto.createHash('md5').update(fs.readFileSync(subpath)).digest('hex');
                    compressed = path.extname(subpath).toLowerCase() === '.zip';
    
                    relative = path.relative(this.assetsRootPath, subpath);
                    relative = relative.replace(/\\/g, '/');
                    relative = encodeURI(relative);
                    obj[relative] = {
                        'size': size,
                        'md5': md5
                    };
                    if (compressed) {
                        obj[relative].compressed = true;
                    }
                }
            }
        } catch (err) {
            console.error(err)
        }
    }

    // 拷贝文件
    copyFile(srcPath, tarPath, cb?) {
        var rs = fs.createReadStream(srcPath)
        rs.on('error', function(err) {
            if (err) {
            console.log('read error', srcPath)
            }
            cb && cb(err)
        })
        
        var ws = fs.createWriteStream(tarPath)
        ws.on('error', function(err) {
            if (err) {
            console.log('write error', tarPath)
            }
            cb && cb(err)
        })
        ws.on('close', function(ex) {
            cb && cb(ex)
        })
        
        rs.pipe(ws)
    }

    // 拷贝目录
    copyFileDir(srcDir, tarDir, cb?) {
        var that = this;
        fs.readdir(srcDir, function(err, files) {
            files.forEach(function(file) {
                var srcPath = path.join(srcDir, file)
                var tarPath = path.join(tarDir, file)
                
                fs.stat(srcPath, function(err, stats) {
                    if (stats.isDirectory()) {
                        // console.log('mkdir', tarPath)
                        fs.mkdir(tarPath, function(err) {
                        if (err) {
                        console.log(err)
                        return
                        }
                    
                        that.copyFileDir(srcPath, tarPath)
                        })
                    } else {
                        that.copyFile(srcPath, tarPath)
                    }
                });
            });
        
            files.length === 0 && cb && cb();
        });
    }

    // 创建目录
    mkdirSync(path) {
        try {
            fs.mkdirSync(path);
        } catch (e) {
            if (e.code != 'EEXIST') throw e;
        }
    }

    // 递归删除目录及文件
    deleteDirSync(dirName) {
        var files = [];
        if (fs.existsSync(dirName)) {
            // 返回文件和子目录的数组
            files = fs.readdirSync(dirName);
            files.forEach((file) => {
                var curPath = path.join(dirName, file);
                // 同步读取文件夹文件，如果是文件夹，在重复触发函数
                if (fs.statSync(curPath).isDirectory()) {
                    this.deleteDirSync(curPath);
                }
                else {
                    fs.unlinkSync(curPath);
                }
            });

            // 清除文件夹
            fs.rmdirSync(dirName);
        }
    };

    // 生成config
    generateConfig(options) {
        var packageOptions = options.packages[PACKAGE_NAME];
        var hotUpdateBuildNum = !isNaN(Number(packageOptions.hotUpdateBuildNum)) ? Number(packageOptions.hotUpdateBuildNum) : 1;
        var hotUpdateVersion = `${packageOptions.hotUpdateVersion.trim()}.${hotUpdateBuildNum.toFixed()}`;
        var config = {
            hotUpdateAddress: packageOptions.hotUpdateAddress,
            hotUpdateVersion: hotUpdateVersion,
            hotupdateSavePath: packageOptions.hotupdateSavePath
        }
        var hotUpdateConfigPath = path.join(this.projectPath, "assets/resources/hotUpdateConfig.json");
        if (fs.existsSync(hotUpdateConfigPath)) {
            fs.unlinkSync(hotUpdateConfigPath);
        }
        
        fs.writeFile(hotUpdateConfigPath, JSON.stringify(config), (err) => {
            if (err) throw err;
            console.log('[' + PACKAGE_NAME + '] hotUpdateConfig.json生成成功');
        });
    }
}

export var hotUpdateTool = new HotUpdateTool();